/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit;

import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getArguments;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.draw2d.AbsoluteBendpoint;
import org.eclipse.draw2d.Bendpoint;
import org.eclipse.draw2d.BendpointConnectionRouter;
import org.eclipse.draw2d.ConnectionRouter;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MidpointLocator;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.PolylineDecoration;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.tools.CellEditorLocator;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.MessageEnd;
import org.eclipse.uml2.uml.MessageSort;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.OpaqueExpression;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.ValueSpecification;

import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.handlers.LifelineLinkHandler;
import com.cea.papyrus.diagram.sequence.sedi.edit.manager.MessageEditManager;
import com.cea.papyrus.diagram.sequence.sedi.edit.manager.MessageLabelLocator;
import com.cea.papyrus.diagram.sequence.sedi.edit.policy.DeletionEditPolicy;
import com.cea.papyrus.diagram.sequence.sedi.edit.policy.LinkEndpointEditPolicy;
import com.cea.papyrus.diagram.sequence.sedi.edit.policy.MessageDirectEditPolicy;
import com.cea.papyrus.diagram.sequence.sedi.editor.message.MessageParser;
import com.cea.papyrus.diagram.sequence.sedi.editor.message.MessageUtil;
import com.cea.papyrus.diagram.sequence.sedi.figures.SeDiAnchor;
import com.cea.papyrus.diagram.sequence.sedi.model.command.diagram.HintRegister;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Edit part for Messages.
 * 
 * @author Andreas Limyr
 * @author frankd@ifi.uio.no
 */
public class MessageEditPart extends AbstractLinkEditPart<Message> {

    public MessageEditPart(GraphEdge view, ModelManager modelManager) {
        super(view, modelManager);
    }

    protected void createEditPolicies() {
        installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new LinkEndpointEditPolicy());
//        installEditPolicy(EditPolicy.CONNECTION_ROLE, new MessageEditPolicy());
        installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE, new MessageDirectEditPolicy());
        
        // Add edit policy to handle deletion requests
		installEditPolicy(
				EditPolicy.COMPONENT_ROLE,
		        new DeletionEditPolicy());
    }

    protected IFigure createFigure() {
        final Message message = getUmlElement();
        final PolylineConnection figure = new PolylineConnection();
        final MidpointLocator locator = new MidpointLocator(figure, 0);
        locator.setRelativePosition(PositionConstants.NORTH);
        locator.setGap(3);
        figure.add(new Label(MessageParser.format(message)), locator);
        figure.setConnectionRouter(new BendpointConnectionRouter());
        return figure;
    }

    @Override
    protected void refreshVisuals() {
        final Message message = getUmlElement();
        final PolylineConnection figure = (PolylineConnection) getFigure();
        figure.setLineWidth(1);

        PolylineDecoration polylineDecoration = new PolylineDecoration();
        PolygonDecoration polygonDecoration = new PolygonDecoration();

        // update name
        Label label = (Label) figure.getChildren().get(0);
        label.setText(MessageParser.format(message));
        // set a suitable target decoration
        switch (message.getMessageSort().getValue()) {
        case MessageSort.ASYNCH_SIGNAL:
        case MessageSort.ASYNCH_CALL:
            figure.setLineStyle(Graphics.LINE_SOLID);
            figure.setTargetDecoration(polylineDecoration);
            break;
        case MessageSort.SYNCH_CALL:
            figure.setLineStyle(Graphics.LINE_SOLID);
            figure.setTargetDecoration(polygonDecoration);
            break;
        case MessageSort.REPLY:
        case MessageSort.CREATE_MESSAGE:
            figure.setLineStyle(Graphics.LINE_DASH);
            figure.setTargetDecoration(polylineDecoration);
            break;
        case MessageSort.DELETE_MESSAGE:
            figure.setLineStyle(Graphics.LINE_SOLID);
            figure.setTargetDecoration(polylineDecoration);
            break;
        default: // For UML 2.2
            // TODO log a small WARN message
            figure.setLineStyle(Graphics.LINE_SOLID);
            figure.setTargetDecoration(polygonDecoration);
        }
        if (figure.getSourceAnchor().getOwner() == figure.getTargetAnchor().getOwner()) {
            SeDiAnchor sourceAnchor = (SeDiAnchor) this.getSourceConnectionAnchor();
            SeDiAnchor targetAnchor = (SeDiAnchor) this.getTargetConnectionAnchor();
            Point ownerLocation = sourceAnchor.getOwner().getBounds().getLocation();
            ownerLocation.translate(new Dimension(
                    sourceAnchor.getOwner().getParent().getBounds().x + 6, sourceAnchor.getOwner()
                            .getParent().getBounds().y + 4));
            Point sourcePosition = new Point(sourceAnchor.getOffsetX(), sourceAnchor.getOffsetY());
            Point targetPosition = new Point(targetAnchor.getOffsetX(), targetAnchor.getOffsetY());
            sourcePosition = sourcePosition.translate(ownerLocation);
            targetPosition = targetPosition.translate(ownerLocation);
            figure.setConnectionRouter(new BendpointConnectionRouter());
            Bendpoint p1 = new AbsoluteBendpoint(sourcePosition);
            Bendpoint p4 = new AbsoluteBendpoint(targetPosition);
            Bendpoint p2 = new AbsoluteBendpoint(p1.getLocation().x + 20, p1.getLocation().y);
            Bendpoint p3 = new AbsoluteBendpoint(p4.getLocation().x + 20, p4.getLocation().y);
            Bendpoint[] bendpoint = {
                     p2, p3
            };
            figure.setRoutingConstraint(Arrays.asList(bendpoint));
        } else {
            List<Bendpoint> list = new ArrayList<Bendpoint>(2);
            list.add(new AbsoluteBendpoint(figure.getSourceAnchor().getLocation(null)));
            list.add(new AbsoluteBendpoint(figure.getTargetAnchor().getLocation(null)));
            figure.setRoutingConstraint(list);
            figure.setConnectionRouter(ConnectionRouter.NULL);
        }

        if (figure.getSourceAnchor().getOwner() == figure.getTargetAnchor().getOwner()) {
            polygonDecoration.setRotation(90.0);
            polylineDecoration.setRotation(90.0);
        }

        super.refreshVisuals();
    }

    // --- Direct edit ---

    @Override
    public void performRequest(Request request) {
        if (request.getType() == RequestConstants.REQ_DIRECT_EDIT)
            performDirectEdit();
        else
            super.performRequest(request);
    }

    protected void performDirectEdit() {
        new MessageEditManager(this, getCellEditorLocator()).show();
    }

    protected CellEditorLocator getCellEditorLocator() {
        return new MessageLabelLocator((PolylineConnection) getFigure());
    }

    // --- Model events ---

    protected void umlModelChanged(Notification evt) {
        final GraphEdge link = getLinkModel();

        switch (evt.getFeatureID(UMLPackage.class)) {
        case UMLPackage.MESSAGE__NAME:
        case UMLPackage.MESSAGE__MESSAGE_SORT:
        case UMLPackage.MESSAGE__EANNOTATIONS:
            refreshVisuals();
            break;

        case UMLPackage.MESSAGE__ARGUMENT: {
            deactivate(evt.getOldValue());
            activate(evt.getNewValue());
            refreshVisuals();
        }

        case UMLPackage.MESSAGE__SEND_EVENT: {
            // == case OPAQUE_EXPRESSION__BODY:
            if (evt.getNewValue() instanceof MessageEnd) {
                // case Message.SendEvent
                final MessageEnd event = (MessageEnd) evt.getNewValue();
                if (event != null) {
                    final GraphNode eventOwner = getMessageEndOwner(event);
                    String anchor =
                            selectAnchor(eventOwner, event, (String) HintRegister
                                    .consumeHint("source"));
                    ModelUtil.setSourceRef(link, anchor);
                    link.setAnchorSource(eventOwner);
                    refresh();
                }
            } else if (evt.getNewValue() instanceof String) {
                // case OpaqueExpression.Body
                refreshVisuals();
            }
            break;
        }

        case UMLPackage.MESSAGE__RECEIVE_EVENT: {
            final MessageEnd event = (MessageEnd) evt.getNewValue();
            if (event != null) {
                final GraphNode eventOwner = getMessageEndOwner(event);
                String anchor =
                        selectAnchor(eventOwner, event, (String) HintRegister.consumeHint("target"));
                ModelUtil.setTargetRef(link, anchor);
                link.setAnchorTarget(eventOwner);
                refresh();
            }
            break;
        }
        }
    }

    @SuppressWarnings("unchecked")
    private static String selectAnchor(final GraphNode view, final MessageEnd event,
            final String anchorHint) {
        String anchorRef = anchorHint;
        if (anchorHint == null) {
            if (view != null) {
                if (ModelUtil.getSemantic(view) instanceof Lifeline)
                    anchorRef = new LifelineLinkHandler().selectAnchor(view, event);
            } else {
            	String errorMessage = "Assertion failed: method=MessageEditPart#selectAnchor, view="
                    + view + ", event=" + event + ", anchorHint=" + anchorHint;
            	Debug.log(new Exception(errorMessage));
                anchorRef = "A1";
            }
        }
        assert anchorRef != null;
        return anchorRef;
    }

    private GraphNode getMessageEndOwner(final MessageEnd end) {
        if (end == null)
            return null;

        final NamedElement owner = MessageUtil.getOwner(end);
        if (owner == null)
            return null;

        return ModelUtil.getGraphNode(owner, LookForElement.getDiagram(getLinkModel()));
    }

    // --- Activation ---

    @Override
    public void activate() {
        super.activate();
        activate(getUmlElement());
    }

    @SuppressWarnings("unchecked")
    public void activate(Object obj) {
        if (obj instanceof Message) {
            Message message = (Message) obj;
            for (ValueSpecification spec : getArguments(message))
                activate(spec);
        } else if (obj instanceof OpaqueExpression)
            ((EObject) obj).eAdapters().add(umlAdapter);
    }

    @Override
    public void deactivate() {
        deactivate(getUmlElement());
        super.deactivate();
    }

    private void deactivate(Object obj) {
        if (obj instanceof Message) {
            Message message = (Message) obj;
            for (ValueSpecification spec : getArguments(message))
                deactivate(spec);
        } else if (obj instanceof OpaqueExpression)
            ((EObject) obj).eAdapters().remove(umlAdapter);
    }
}
